CloudFormationで作成するリソースにはRoute 53で独自DNS名を付けよ
よく訓練されたアップル信者、都元です。
CloudFormationで環境を定義する際、作成済み *1のRoute 53のHostedZone名をパラメータとして与えて、各要素(EC2インスタンス, ELB, RDS等)に名前付けを行うのは、常套手段です。【AWS】VPC環境構築ノウハウ社内資料 2014年4月版でもご紹介しましたが、VPC内のインスタンスに対するプライベートIPアドレスは制御しないポリシーです。また、パブリックなIPアドレスやDNS名も、そもそも制御できるものではありません。したがって、参照が必要なインスタンスには、Route 53を用いて適宜名前を付けてやると、運用が楽になります。
オブジェクト指向プログラミングに慣れた人に対しては、オブジェクト(AWSから割り当てられたIPアドレスやDNS名)を直接触るんじゃなくて、インターフェイス(独自ドメインのDNS名)を介して触ることによって、コンポーネント間が疎結合になる、といった表現で伝わるかもしれません。
EC2編
例えば、踏み台 (bastion) インスタンスを立てる場合。以下のようにして、EIPに対するAレコードと、(自動付与の)プライベートIPアドレスに対するAレコードを両方つけたりします。まぁ後者は必要なければ省略しても全く問題ありません。こうすることにより、HostedZoneパラメータとしてexample.comを受け取った場合、このインスタンスにはbastion.example.comという名前が付き、この名前で外からアクセスできるようになります。
{ "AWSTemplateFormatVersion": "2010-09-09", "Parameters" : { "HostedZone" : { "Default" : "", "Description" : "The alternative domain name of this distribution.", "Type" : "String" }, }, "Resources": { "BastionInstance": { "Type": "AWS::EC2::Instance", "Properties": { ...略... } }, "BastionInstanceEIP": { "Type": "AWS::EC2::EIP", "Properties": { "Domain": "vpc", "InstanceId": { "Ref" : "BastionInstance" } } }, "BastionDNSRecord" : { "Type" : "AWS::Route53::RecordSet", "Properties" : { "HostedZoneName" : { "Fn::Join" : [ "", [ { "Ref" : "HostedZone" }, "." ]]}, "Comment" : "A record for the Bastion instance.", "Name" : { "Fn::Join" : [ "", [ "bastion.", { "Ref" : "HostedZone" }, "." ]]}, "Type" : "A", "TTL" : "300", "ResourceRecords" : [ { "Ref" : "BastionInstanceEIP" } ] } }, "BastionLocalDNSRecord" : { "Type" : "AWS::Route53::RecordSet", "Properties" : { "HostedZoneName" : { "Fn::Join" : [ "", [ { "Ref" : "HostedZone" }, "." ]]}, "Comment" : "A record for the private IP address of Bastion instance.", "Name" : { "Fn::Join" : [ "", [ "bastion.local.", { "Ref" : "HostedZone" }, "." ]]}, "Type" : "A", "TTL" : "300", "ResourceRecords" : [ { "Fn::GetAtt" : [ "BastionInstance", "PrivateIp" ] } ] } } } }
RDS編
これも何も難しい話ではありません。概ねEC2インスタンスと同じです。最も大きな違いとしては、AレコードではなくCNAMEレコードとしている点です。この定義により、db.local.example.comでRDSに接続できるようになります。
"DatabaseInstance" : { "Type" : "AWS::RDS::DBInstance", "DeletionPolicy" : "Snapshot", "Properties" : { ...略... } }, "DatabaseDNSRecord" : { "Type" : "AWS::Route53::RecordSet", "Properties" : { "HostedZoneName" : { "Fn::Join" : [ "", [ { "Ref" : "HostedZone" }, "." ]]}, "Comment" : "CNAME record for the db.", "Name" : { "Fn::Join" : [ "", [ "db.local.", { "Ref" : "HostedZone" }, "." ]]}, "Type" : "CNAME", "TTL" : "300", "ResourceRecords" : [ { "Fn::GetAtt" : [ "DatabaseInstance", "Endpoint.Address" ] } ] } },
ELB編
ELBにも名前をつけておきましょう。この定義でelb.example.comでロードバランサにアクセスできます。
これはAレコードでもCNAMEレコードでもなく、ALIASレコードとして定義しているのが特徴です。ALIASレコードについて、詳しくはAmazon Route 53のALIASレコード利用のススメを御覧ください。
"ElasticLoadBalancer" : { "Type" : "AWS::ElasticLoadBalancing::LoadBalancer", "Properties" : { ...略... } }, "LoadBalancerDNSRecord" : { "Type" : "AWS::Route53::RecordSet", "Properties" : { "HostedZoneName" : { "Fn::Join" : [ "", [ { "Ref" : "HostedZone" }, "." ]]}, "Comment" : "ALIAS targeted to LoadBalancer.", "Name" : { "Fn::Join" : [ "", [ "elb", { "Ref" : "HostedZone" }, "." ]]}, "Type" : "A", "AliasTarget" : { "HostedZoneId" : { "Fn::GetAtt" : [ "ElasticLoadBalancer", "CanonicalHostedZoneNameID" ] }, "DNSName" : { "Fn::GetAtt" : [ "ElasticLoadBalancer","CanonicalHostedZoneName" ] } } } },
CloudFront編
CloudFrontも、Distributionに対してAWSからDNS名が割り当てられます。これも直接使うのではなく、独自ドメインを挟んでおきましょう。この定義でassets.example.comがCloudFrontに繋がります。
ポイントは2つ。CloudFrontで独自ドメインを使う場合は、AliasesプロパティにDNS名を指定する必要があります(5〜7行目)。また、CloudFrontもALIASレコードに対応しているため、AliasTargetを指定するのですが、cloudfront.netのHosted Zone IDは、現状Z2FDTNDATAQYW2で固定です。ハードコーディングしてしまいましょう。
"AssetsDistribution" : { "Type" : "AWS::CloudFront::Distribution", "Properties" : { "DistributionConfig" : { "Aliases" : [ { "Fn::Join" : [ "", [ "assets.", { "Ref" : "HostedZone" } ]]} ], ...略... } } }, "AssetsGlobalDNSRecord" : { "Type" : "AWS::Route53::RecordSet", "Properties" : { "HostedZoneName" : { "Fn::Join" : [ "", [ { "Ref" : "HostedZone" }, "." ]]}, "Comment" : "ALIAS record for the assets distribution.", "Name" : { "Fn::Join" : [ "", ["assets.", { "Ref" : "HostedZone" }, "." ]]}, "Type" : "A", "AliasTarget" : { "HostedZoneId" : "Z2FDTNDATAQYW2", "DNSName" : { "Fn::GetAtt" : [ "AssetsDistribution", "DomainName" ]} } } },
Elastic Beanstalk 妄想編
さて、豆の木ですよ。Elastic BeanstalkのEnvironmentをCloudFormationに依らず普通に作成すると、その環境には*.elasticbeanstalk.comというDNS名が与えられます。本来は CloudFront のケースと同様に、このDNS名に対するALIASレコードを構成できるのがベストな状況です。しかし残念ながら現状、Route 53及びElastic BeanstalkはこのDNS名のエイリアスに対応していません。(対応お待ちしております!)
また、CloudFormationの "AWS::ElasticBeanstalk::Environment" 型のリソースに対し、{ "Fn::GetAtt" : [ "EBEnvironment", "EndpointURL" ]}とすることで、その環境にアクセスするためのDNS名を得ることができます。ここで取得できるDNS名は、本来はElastic BeanstalkのDNS名が返ってくるのがベストな状況です。しかし残念ながら現状、*.elasticbeanstalk.comではなく、Beanstalkの内部的に作られるELBのDNS名(例: awseb-myst-myen-132MQC4KRLAMD-1371280482.us-east-1.elb.amazonaws.com)となっています。(対応お待ちしております!)
というわけで理想は下記のようなものですが、まぁあくまでも妄想なので動きません。
これは妄想 "EBEnvironment" : { "Type" : "AWS::ElasticBeanstalk::Environment", "Properties" : { ...略... } }, これは妄想 "EBLoadBalancerDNSRecord" : { "Type" : "AWS::Route53::RecordSet", "Properties" : { "HostedZoneName" : { "Fn::Join" : [ "", [ { "Ref" : "HostedZone" }, "." ]]}, "Comment" : "ALIAS record for EB load balancer.", "Name" : { "Fn::Join" : [ "", [ "eb.", { "Ref" : "HostedZone" }, "." ]]}, "Type" : "A", "AliasTarget" : { "HostedZoneId" : { "Fn::GetAtt" : [ "EBEnvironment", "HostedZoneId" ]}, ← ここが妄想! "example.elasticbeanstalk.com" が返る妄想! "DNSName" : { "Fn::GetAtt" : [ "EBEnvironment", "DNSName" ]} ← ここが妄想! "elasticbeanstalk.com" のHosted Zone IDが返る妄想! } } }, これは妄想
Elastic Beanstalk ワークアラウンド編
ではどうすればいいか。よく訓練されたアップル信者、都元が頑張りました。{ "Fn::GetAtt" : [ "EBEnvironment", "EndpointURL" ]}で取得できるDNS名が、ELBのDNS名であることが幸いでした。
先ほど、「cloudfront.netのHosted Zone IDは、現状Z2FDTNDATAQYW2で固定です」と説明しました。同じように、ap-northeast-1.elb.amazonaws.comのHosted Zone IDは、Z2YN17T5R711GTで固定であることが分かりました。
ただし、CloudFrontはリージョンに対するサービスではないので、グローバルにHosted Zone IDが1つしかありません。一方、ELBはリージョン毎にゾーンが異なるため、それぞれにHosted Zone IDがありました。リージョン毎に違う値を選ぶ場合はMappingsですね! 各リージョンにおけるELBの Hosted Zone ID を調べました!
"Mappings" : { "ELBDomain": { "us-east-1": { "HostedZoneId": "Z3DZXE0Q79N41H" }, "us-west-2": { "HostedZoneId": "Z33MTJ483KN6FU" }, "us-west-1": { "HostedZoneId": "Z1M58G0W56PQJA" }, "eu-west-1": { "HostedZoneId": "Z3NF1Z3NOM5OY2" }, "ap-southeast-1": { "HostedZoneId": "Z1WI8VXHPB1R38" }, "ap-southeast-2": { "HostedZoneId": "Z2999QAZ9SRTIC" }, "ap-northeast-1": { "HostedZoneId": "Z2YN17T5R711GT" }, "sa-east-1": { "HostedZoneId": "Z2ES78Y61JGQKS" } } }
このようにMappingsを定義し、以下のように書けば、Elastic Beanstalkが内部的に生成するELBに対するALIASレコードが定義できます。うほほい。
"EBEnvironment" : { "Type" : "AWS::ElasticBeanstalk::Environment", "Properties" : { ...略... } }, "EBLoadBalancerDNSRecord" : { "Type" : "AWS::Route53::RecordSet", "Properties" : { "HostedZoneName" : { "Fn::Join" : [ "", [ { "Ref" : "HostedZone" }, "." ]]}, "Comment" : "ALIAS record for EB load balancer.", "Name" : { "Fn::Join" : [ "", [ "eb.", { "Ref" : "HostedZone" }, "." ]]}, "Type" : "A", "AliasTarget" : { "HostedZoneId" : { "Fn::FindInMap" : [ "ELBDomain", { "Ref": "AWS::Region" }, "HostedZoneId" ]}, "DNSName" : { "Fn::GetAtt" : [ "EBEnvironment", "EndpointURL" ]} } } },
というわけで、結構ワークアラウンドな感じですが、綺麗に動いています。一つだけ注意点として、この方法を使ったとしてもElastic BeanstalkのSwap Environment URL機能は使えません。残念orz
まとめ
なんだか無駄に見えるかもしれませんが、とにかく名前は付けておきましょう。独自ドメインで。従って、ある程度まとまったシステムのテンプレートには、パラメータとして必ずHostedZoneがある、と思っておくと良いでしょう。
脚注
- そして権威DNSとして上位に登録済み。 ↩